package com.yangyouli.unity_demo_server.container;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.yangyouli.unity_demo_server.annotation.Inject;
import com.yangyouli.unity_demo_server.annotation.Injectable;
import com.yangyouli.unity_demo_server.annotation.InjectableDao;
import com.yangyouli.unity_demo_server.util.ConnUtil;
import com.yangyouli.unity_demo_server.util.PropertiesHelper;
import com.yangyouli.unity_demo_server.util.ReadStringXml;
import com.yangyouli.unity_demo_server.util.ScannerUtil;
import com.yangyouli.unity_demo_server.util.StringUtil;

/**
 * 该类是一个容器，可以获得被 {@link@Injectable} 注解的对象实例
 * @author 24279
 *
 */
public class Container {
	private static Container _instance;
	private ControllerContainer controllerContainer;
	// 存储被 @Injectable 注解的类的实例
	private Map<String, Object> instanceMap = new HashMap<String, Object>();
	
	// 存储类中的方法配置了事务管理的类
	// private List<Class<?>> txClassList = new ArrayList<>();

	// 存储要实例和注入的对象和事务管理所需要扫描的包在 xml 中的配置路径
	private String[] nodePaths = new String[] { "/container/inject-package/package"/*, "/container/transaction/package"*/ };
	
	private SqlSessionTemplate sqlSessionTemplate;
	
	Logger log = Logger.getLogger(Container.class);
	
	private Container () {
		init();
	}
	
	/**
	 * 得到实例对象
	 * @return
	 */
	public static Container getInstance () {
		if (_instance == null) {
			synchronized (Container.class) {
				if (_instance == null) {
					_instance = new Container();
				}
			}
		}
		return _instance;
	}
	
	private void init () {
		sqlSessionTemplate = new SqlSessionTemplate(ConnUtil.getSqlSessionFactory());
		// 得到 ContainerConfig.xml 配置文件的路径
		String configPath = PropertiesHelper.getPropertiesValue("ContainerConfig");
		// 得到要扫描的包的路径
		Map<String, List<String>> pathMap = ReadStringXml.getNodesValues(configPath, nodePaths);
		
		// 得到依赖注入所要扫描的包
		List<String> diPath = pathMap.get(nodePaths[0]);
		
		// 得到事物管理所要扫描的包
		//List<String> txPath = pathMap.get(nodePaths[1]);
		
		// 扫描并得到被 Injectable 注解的类的实例
		for (String packagePath : diPath) {
			getClassInstance(packagePath);
		}
		
		// 实例化 Controller 容器
		controllerContainer = new ControllerContainer();
		
		// 依次给 instanceMap 中的属性注入值
		for (Object o : instanceMap.values()) {
			injectInstance(o);
		}
		
		// 给 controllerContainer 中的容器注入值
		for (Object o : controllerContainer.getControllerMap().values()) {
			injectInstance(o);
		}
		
		// 扫描并得到被 Transaction 注解的方法
		/*for (String packagePath : txPath) {
			getTxMethodOfClass(packagePath);
		}*/
	}
	
	/**
	 * 当对象的某个属性被 {@link@Inject} 注解时，注入值
	 * @param obj 要注入属性的对象
	 * @param valueObj
	 */
	void injectInstance (Object obj) {
		try {
			Class<?> clazz = obj.getClass();
			// 如果本类的某个属性被 Inject 注解，则将对应的对象注入这个属性
			Field[] fields = clazz.getDeclaredFields();
			for (Field f : fields) {
				// 让私有属性也可被注入值
				f.setAccessible(true);
				if (f.isAnnotationPresent(Inject.class)) {
					String value = getAnnotationValueOrFieldName(f, Inject.class);
					// 根据 value 得到对象
					Object o = getBean(value);
					Class<?> fClass = f.getType();
					// 如果类型匹配，则注入
					if (o != null && fClass.isInstance(o)) {
						f.set(obj, o);
					} else {
						// 类型不匹配，根据类型注入
						injectByType(obj, f);
					}
				}
			}
		} catch (IllegalArgumentException | IllegalAccessException e) {
			log.error("当对象的某个属性被 {@link@Inject} 注解时，注入值时发生异常", e);
		}
	}
	
	/**
	 * 获取容器中的 bean 实例
	 * @param name
	 * @return
	 */
	public Object getBean (String name) {
		return instanceMap.get(name);
	}
	
	/**
	 * 获取容器中的 bean 实例
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T getBean (Class<T> clazz) {
		for (Object o : instanceMap.values()) {
			if (clazz.isInstance(o)) {
				return (T)o;
			}
		}
		// 找不到匹配类型的对象
		throw new RuntimeException("属性注入失败，在容器没有找到类型为 [" + clazz + "] 的对像");
	}
	
	public Object getController (String controllerName) {
		return controllerContainer.getController(controllerName);
	}
	
	public Method getControllerMethod (String controllerName, String methodName) {
		return controllerContainer.getMethod(controllerName, methodName);
	}
	
	/**
	 * 得到该包下的某类中被 {@link}Transaction 注解的方法，添加到 txClassList 中
	 * @param packagePath
	 */
	/*private void getTxMethodOfClass (String packagePath) {
		List<String> classWholeName = ScannerUtil.scanPackage(packagePath);
		
		try {
			// 存储接口下被 Transaction 注解的方法的接口
			for (String name : classWholeName) {
				List<Method> beAnnotatedByTransaction = new ArrayList<>();
				
				Class<?> clazz = Class.forName(name);
				Method[] methods = clazz.getMethods();
				
				for (Method m : methods) {
					if (m.isAnnotationPresent(Transaction.class)) {
						beAnnotatedByTransaction.add(m);
						txClassList.add(clazz);
						if (!clazz.isInterface()) {
							try {
								throw new Exception ("Transaction 注解只能添加在接口的方法上");
							} catch (Exception e) {
								e.printStackTrace();
								return;
							}
						}
						break;
					}
				}
			}
			
			// 将 instanceMap 中实现了这些接口的类代理起来
			for (Object o : instanceMap.values()) {
				Class<?>[] interfaces = o.getClass().getInterfaces();
				for (Class<?> i : interfaces) {
					if (txClassList.contains(i)) {
						// 改对象有接口实现了被事物注解的方法, 代理它
						TransactionInterceptor ti = new TransactionInterceptor(o);
						o = ti.getObject();
					}
				}
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}*/
	
	/**
	 * 得到该包下的所有被{@link@Injectable}注解的类的实例，添加到 instanceMap 中
	 */
	private void getClassInstance (String packagePath) {
		List<String> classWholeName = ScannerUtil.scanPackage(packagePath);
		
		try {
			for (String name : classWholeName) {
				Class<?> clazz = Class.forName(name);
				// 如果本类被 Injectable 注解，则将其实例加入到 instanceMap 中
				if (clazz.isAnnotationPresent(Injectable.class)) {
					// 得到类被注解的值，为空字符则为类名且首字母小写
					String value = getAnnotationValueOrClassName(clazz, Injectable.class);
					Object o = clazz.newInstance();
					log.debug("创建对象：" + clazz.getName());
					instanceMap.put(value, o);
				}
				// 如果本类被 InjectableDao 注解，则将其实例加入到 instanceMap 中
				if (clazz.isAnnotationPresent(InjectableDao.class)) {
					// 得到类被注解的值，为空字符则为类名且首字母小写
					String value = clazz.getAnnotation(InjectableDao.class).value();
					if (value.equals("")) {
						value = StringUtil.firstWordToLower(clazz.getSimpleName());
					}
					// 通过 session 得到代理接口实现类
					Object o = sqlSessionTemplate.getMapper(clazz);
					log.debug("创建 dao 的代理对象：" + clazz.getName());
					instanceMap.put(value, o);
				}
			}
		} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
			log.error("得到该包下的所有被{@link@Injectable}注解的类的实例，添加到 instanceMap 中发生异常", e);
		}
	}
	
	private void injectByType (Object obj, Field f) throws IllegalArgumentException, IllegalAccessException {
		// 在容器中找到类型与字段的类型匹配的对象
		Class<?> fClass = f.getType();
		f.set(obj, getBean(fClass));
	}
	
	/**
	 * 返回被注解的类上的注解的 value 值，如果 value 为空，则返回类名（首字母小写）
	 * @param controllerClass
	 * @param annotationClass
	 * @return
	 */
	private String getAnnotationValueOrClassName (Class<?> clazz, Class<Injectable> annotationClass) {
		String value = null; 
		// 得到注解的 value, 赋值给 identify
		value = clazz.getAnnotation(annotationClass).value();
		// 如果没有得到注解内的 value 值，则赋值为类的名称，首字母小写
		if (value.equals("") || value == null) {
			String className = clazz.getSimpleName();
			// 将 className 的第一个单词变为小写并赋值给 value
			value = StringUtil.firstWordToLower(className);
		}
		return value;
	}
	
	/**
	 * 返回被注解的字段上的注解的 value 值，如果 value 为空，则返回字段得到类型名(首字母小写)
	 * @param f
	 * @param annotationClass
	 * @return
	 */
	private String getAnnotationValueOrFieldName (Field f, Class<Inject> annotationClass) {
		String value = null; 
		// 得到注解 value, 赋值给 identify
		value = f.getAnnotation(annotationClass).value();
		// 如果没有得到注解内的 value 值，则赋值为字段的类型名称
		if (value == null || value.equals("")) {
			String className = f.getType().getSimpleName();
			// 将 className 的第一个单词变为小写并赋值给 value
			value = StringUtil.firstWordToLower(className);
		}
		return value;
	}
}
